/*
    Copyright (C) 2025 mio <stigma@disroot.org>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/
#include "aster_videoplayer.h"

#include <tqapplication.h>

#include <vlc/vlc.h>

#include "playback_state.h"
#include "playlist.h"

#define VLC_MEDIA_PARSED_DONE           (TQEvent::User + libvlc_MediaParsedChanged)
#define VLC_MEDIAPLAYER_LENGTHCHANGED   (TQEvent::User + libvlc_MediaPlayerLengthChanged)
#define VLC_MEDIAPLAYER_TIMECHANGED     (TQEvent::User + libvlc_MediaPlayerTimeChanged)
#define VLC_MEDIAPLAYER_ENDREACHED      (TQEvent::User + libvlc_MediaPlayerEndReached)
#define VLC_MEDIAPLAYER_PAUSED          (TQEvent::User + libvlc_MediaPlayerPaused)
#define VLC_MEDIAPLAYER_PLAYING         (TQEvent::User + libvlc_MediaPlayerPlaying)
#define VLC_MEDIAPLAYER_STOPPED         (TQEvent::User + libvlc_MediaPlayerStopped)

constexpr int MOUSE_HIDE_TIMER_MS = 2000;

static void vlc_event_callback(const libvlc_event_t *ev, void *data)
{
    if (!data) {
        return;
    }

    auto player = reinterpret_cast<VideoPlayer *>(data);

    if (ev->type == libvlc_MediaParsedChanged) {
        if (ev->u.media_parsed_changed.new_status == libvlc_media_parsed_status_done) {
            TQCustomEvent *e = new TQCustomEvent(VLC_MEDIA_PARSED_DONE);
            TQApplication::postEvent(player, e);
        }
        return;
    }

    TQCustomEvent *e = new TQCustomEvent(TQEvent::User + ev->type);
    TQApplication::postEvent(player, e);
}

VideoPlayer::VideoPlayer(TQWidget *parent, const char *name)
    : TQWidget(parent, name)
    , m_mediaPlayer(nullptr)
    , m_mouseTimer(-1)
    , m_playlist(nullptr)
{
    setPaletteBackgroundColor(TQt::black);
    setUpdatesEnabled(false);
    setMouseTracking(true);

    m_instance = libvlc_new(0, nullptr);
    if (!m_instance) {
        tqFatal("Unable to initialize libvlc");
    }
}

VideoPlayer::~VideoPlayer()
{
    if (m_mediaPlayer) {
        libvlc_media_player_release(m_mediaPlayer);
    }

    libvlc_release(m_instance);
}

int64_t VideoPlayer::currentTime() const
{
    if (m_mediaPlayer) {
        return libvlc_media_player_get_time(m_mediaPlayer);
    }

    return 0;
}


int64_t VideoPlayer::totalTime() const
{
    if (m_mediaPlayer) {
        return libvlc_media_player_get_length(m_mediaPlayer);
    }

    return -1;
}

bool VideoPlayer::isPaused() const
{
    if (m_mediaPlayer) {
        return !libvlc_media_player_is_playing(m_mediaPlayer);
    }

    return false;
}

bool VideoPlayer::isPlaying() const
{
    if (m_mediaPlayer) {
        return libvlc_media_player_is_playing(m_mediaPlayer);
    }

    return false;
}

void VideoPlayer::openStream(const KURL &url)
{
    if (url.isLocalFile()) {
        // TODO: Append to playlist instead of taking over stream.
        libvlc_media_t *media = libvlc_media_new_path(m_instance, url.path().local8Bit().data());
        if (!media) {
            tqWarning("Failed to open stream %s", url.path().local8Bit().data());
            return;
        }

        if (!m_mediaPlayer) {
            setupPlayer();
        }

        libvlc_media_player_stop(m_mediaPlayer);
        libvlc_media_player_set_media(m_mediaPlayer, media);
        libvlc_media_player_play(m_mediaPlayer);

        libvlc_media_release(media);
    } else if (url.url() == "bluray://") {
        libvlc_media_t *media = libvlc_media_new_location(m_instance, "bluray://");
        if (!media) {
            tqWarning("Failed to open Blu-ray device: %s", libvlc_errmsg());
            return ;
        }

        if (!m_mediaPlayer) {
            setupPlayer();
        }

        libvlc_media_player_stop(m_mediaPlayer);
        libvlc_media_player_set_media(m_mediaPlayer, media);
        libvlc_media_player_play(m_mediaPlayer);

        libvlc_media_release(media);
    } else if (url.url() == "dvd://") {
        libvlc_media_t *media = libvlc_media_new_location(m_instance, "dvd://");
        if (!media) {
            tqWarning("Failed to open DVD device: %s", libvlc_errmsg());
            return ;
        }

        if (!m_mediaPlayer) {
            setupPlayer();
        }

        libvlc_media_player_stop(m_mediaPlayer);
        libvlc_media_player_set_media(m_mediaPlayer, media);
        libvlc_media_player_play(m_mediaPlayer);

        libvlc_media_release(media);
    }
}

void VideoPlayer::showCursor()
{
    if (m_mouseTimer != -1) {
        killTimer(m_mouseTimer);
    }
    setCursor(arrowCursor);
    m_mouseTimer = startTimer(MOUSE_HIDE_TIMER_MS);
}

Playlist* VideoPlayer::playlist() const
{
    return m_playlist;
}

void VideoPlayer::setPlaylist(Playlist* playlist)
{
    if (m_playlist) {
        disconnect(m_playlist, nullptr, this, nullptr);
    }

    m_playlist = playlist;
    connect(m_playlist, TQ_SIGNAL(currentItemChanged(const KURL&)), TQ_SLOT(playlistItemChanged(const KURL&)));
}

int VideoPlayer::activeAudioTrack() const
{
    if (!m_mediaPlayer) {
        return -1;
    }

    return libvlc_audio_get_track(m_mediaPlayer);
}

void VideoPlayer::setActiveAudioTrack(int index)
{
    if (!m_mediaPlayer) {
        return;
    }

    if (libvlc_audio_set_track(m_mediaPlayer, index) == -1) {
        tqWarning("Failed to set audio track: %s", libvlc_errmsg());
    }
}

int VideoPlayer::activeSubtitleTrack() const
{
    if (!m_mediaPlayer) {
        return -1;
    }

    return libvlc_video_get_spu(m_mediaPlayer);
}

void VideoPlayer::setActiveSubtitleTrack(int index)
{
    if (!m_mediaPlayer) {
        return;
    }

    if (libvlc_video_set_spu(m_mediaPlayer, index) == -1) {
        tqWarning("Failed to set video SPU: %s", libvlc_errmsg());
    }
}

int VideoPlayer::activeVideoTrack() const
{
    if (!m_mediaPlayer) {
        return -1;
    }

    return libvlc_video_get_track(m_mediaPlayer);
}

void VideoPlayer::setActiveVideoTrack(int index)
{
    if (!m_mediaPlayer) {
        return;
    }

    if (libvlc_video_set_track(m_mediaPlayer, index) == -1) {
        tqWarning("Failed to set video track: %s", libvlc_errmsg());
    }
}

TQValueVector<MediaTrack> VideoPlayer::audioTracks() const
{
    return m_audioTracks;
}

TQValueVector<MediaTrack> VideoPlayer::subtitleTracks() const
{
    return m_subtitleTracks;
}

TQValueVector<MediaTrack> VideoPlayer::videoTracks() const
{
    return m_videoTracks;
}

void VideoPlayer::pause()
{
    if (m_mediaPlayer) {
        libvlc_media_player_pause(m_mediaPlayer);
    }
}

void VideoPlayer::play()
{
    // tqDebug("VideoPlayer::play");
    if (isPlaying()) {
        return;
    }

    // Nothing to play.
    if (m_playlist->isEmpty()) {
        return;
    }

    if (!m_mediaPlayer) {
        setupPlayer();
    }

    // Prepare the first item in the playlist if nothing has been played.
    if (!libvlc_media_player_get_media(m_mediaPlayer)) {
        KURL mrl = m_playlist->currentItem();
        libvlc_media_t *media;

        if (mrl.isLocalFile()) {
            media = libvlc_media_new_path(m_instance, mrl.path().local8Bit().data());
        } else {
            media = libvlc_media_new_location(m_instance, mrl.url().local8Bit().data());
        }

        libvlc_media_player_set_media(m_mediaPlayer, media);

        libvlc_event_manager_t *manager = libvlc_media_event_manager(media);

        libvlc_event_attach(manager, libvlc_MediaParsedChanged, vlc_event_callback, (void *)this);

        libvlc_media_release(media);
    }

    libvlc_media_player_play(m_mediaPlayer);
}

void VideoPlayer::seek(int value)
{
    if (m_mediaPlayer) {
        libvlc_media_player_set_time(m_mediaPlayer, value);
    }
}

void VideoPlayer::setMuted(bool muted)
{
    if (m_mediaPlayer) {
        libvlc_audio_set_mute(m_mediaPlayer, muted);
    }
}

void VideoPlayer::stop()
{
    if (m_mediaPlayer) {
        libvlc_media_player_stop(m_mediaPlayer);
    }
    erase();
}

void VideoPlayer::setVolume(int volume)
{
    if (m_mediaPlayer) {
        libvlc_audio_set_volume(m_mediaPlayer, volume);
    }
}

void VideoPlayer::customEvent(TQCustomEvent *e)
{
    switch ((int)e->type())
    {
        case VLC_MEDIAPLAYER_TIMECHANGED: {
            emit tick(libvlc_media_player_get_time(m_mediaPlayer));
            break;
        }
        case VLC_MEDIAPLAYER_LENGTHCHANGED: {
            emit lengthChanged(libvlc_media_player_get_length(m_mediaPlayer));
            break;
        }
        case VLC_MEDIAPLAYER_STOPPED: {
            erase();
            emit stateChanged(StoppedState);
            break;
        }
        case VLC_MEDIAPLAYER_PLAYING: {
            emit stateChanged(PlayingState);
            break;
        }
        case VLC_MEDIAPLAYER_PAUSED: {
            emit stateChanged(PausedState);
            break;
        }
        case VLC_MEDIAPLAYER_ENDREACHED: {
            if (m_playlist) {
                m_playlist->next();
                play();
            }
            break;
        }
        case VLC_MEDIA_PARSED_DONE: {
            setupTracks();
            break;
        }
        default: {
            tqDebug("Unknown VLC Event: %d", e->type() - TQEvent::User);
            break;
        }
    }
}

void VideoPlayer::mouseMoveEvent(TQMouseEvent *e)
{
    TQWidget::mouseMoveEvent(e);
    showCursor();
}

void VideoPlayer::mousePressEvent(TQMouseEvent *e)
{
    TQWidget::mousePressEvent(e);

    if (m_mediaPlayer) {
        libvlc_media_player_navigate(m_mediaPlayer, libvlc_navigate_activate);
    }
}

void VideoPlayer::timerEvent(TQTimerEvent *event)
{
    if (event->timerId() == m_mouseTimer) {
        killTimer(m_mouseTimer);
        m_mouseTimer = -1;
        setCursor(blankCursor);
    }
    TQWidget::timerEvent(event);
}

void VideoPlayer::setupPlayer()
{
    m_mediaPlayer = libvlc_media_player_new(m_instance);
    libvlc_media_player_set_xwindow(m_mediaPlayer, this->winId());

    // Setup callbacks
    libvlc_event_manager_t *manager = libvlc_media_player_event_manager(m_mediaPlayer);
    libvlc_event_attach(manager, libvlc_MediaPlayerLengthChanged, &vlc_event_callback, (void *)this);
    libvlc_event_attach(manager, libvlc_MediaPlayerTimeChanged, &vlc_event_callback, (void *)this);
    libvlc_event_attach(manager, libvlc_MediaPlayerEndReached, &vlc_event_callback, (void *)this);
    libvlc_event_attach(manager, libvlc_MediaPlayerPaused, &vlc_event_callback, (void *)this);
    libvlc_event_attach(manager, libvlc_MediaPlayerPlaying, &vlc_event_callback, (void *)this);
    libvlc_event_attach(manager, libvlc_MediaPlayerStopped, &vlc_event_callback, (void *)this);
    libvlc_event_attach(manager, libvlc_MediaParsedChanged, &vlc_event_callback, (void *)this);

}

void VideoPlayer::setupTracks()
{
    m_audioTracks.clear();
    m_subtitleTracks.clear();
    m_videoTracks.clear();

    libvlc_track_description_t *tracks = libvlc_audio_get_track_description(m_mediaPlayer);
    libvlc_track_description_t *track = tracks;

    /* VLC includes a "Disable" track with ID '-1' which we skip here */

    while (track) {
        if (track->i_id >= 0) {
            MediaTrack t;
            t.id = track->i_id;
            t.name = track->psz_name;
            m_audioTracks.push_back(t);
        }

        track = track->p_next;
    }

    libvlc_track_description_list_release(tracks);

    tracks = libvlc_video_get_spu_description(m_mediaPlayer);
    track = tracks;

    while (track) {
        if (track->i_id >= 0) {
            MediaTrack t;
            t.id = track->i_id;
            t.name = track->psz_name;
            m_subtitleTracks.push_back(t);
        }

        track = track->p_next;
    }

    libvlc_track_description_list_release(tracks);

    tracks = libvlc_video_get_track_description(m_mediaPlayer);
    track = tracks;

    while (track) {
        if (track->i_id >= 0) {
            MediaTrack t;
            t.id = track->i_id;
            t.name = track->psz_name;
            m_videoTracks.push_back(t);
        }

        track = track->p_next;
    }

    libvlc_track_description_list_release(tracks);

    emit tracksChanged();
}

void VideoPlayer::playlistItemChanged(const KURL& mrl)
{
    // tqDebug("VideoPlayer::playlistItemChanged");
    if (!m_mediaPlayer) {
        return;
    }

    bool continuePlaying = libvlc_media_player_is_playing(m_mediaPlayer);

    libvlc_media_t *media;

    if (mrl.isLocalFile()) {
        media = libvlc_media_new_path(m_instance, mrl.path().local8Bit().data());
    } else {
        media = libvlc_media_new_location(m_instance, mrl.url().local8Bit().data());
    }

    libvlc_media_player_stop(m_mediaPlayer);
    libvlc_media_player_set_media(m_mediaPlayer, media);

    libvlc_event_manager_t *manager = libvlc_media_event_manager(media);
    libvlc_event_attach(manager, libvlc_MediaParsedChanged, vlc_event_callback, (void *)this);

    if (continuePlaying) {
        libvlc_media_player_play(m_mediaPlayer);
    }

    libvlc_media_release(media);
}

#include "aster_videoplayer.moc"
